﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Diagnostics;
using System.Net;
using Common;

namespace Server
{
	class ServerTerminal
	{
		public event TCPTerminal_MessageRecivedDel MessageRecived;
		public event TCPTerminal_ConnectDel ClientConnect;
		public event TCPTerminal_DisconnectDel ClientDisconnect;

		private Socket _socket;
		private bool _closed;

		private Dictionary<long, ConnectedClient> _clients = new Dictionary<long, ConnectedClient>();

		public void StartListen(int port)
		{
			IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, port);

			_socket = new Socket(AddressFamily.InterNetwork,
				SocketType.Stream, ProtocolType.Tcp);

			try
			{
				_socket.Bind(ipLocal);
			}
			catch (Exception ex)
			{
				return;
			}

			_socket.Listen(4);

			// Assign delegate that will be invoked when client connect.
			_socket.BeginAccept(new AsyncCallback(OnClientConnection), null);
		}

		private void OnClientConnection(IAsyncResult asyn)
		{
			if (_closed)
			{
				return;
			}

			try
			{
				Socket clientSocket = _socket.EndAccept(asyn);

				RaiseClientConnected(clientSocket);

				ConnectedClient connectedClient = new ConnectedClient(clientSocket);

				connectedClient.MessageRecived += OnMessageRecived;
				connectedClient.Disconnected += OnClientDisconnection;

				connectedClient.StartListen();

				long key = clientSocket.Handle.ToInt64();
				if (_clients.ContainsKey(key))
				{
				}

				_clients[key] = connectedClient;

				// Assign delegate that will be invoked when next client connect.
				_socket.BeginAccept(new AsyncCallback(OnClientConnection), null);
			}
			catch (Exception sex)
			{
			}
		}

		private void OnClientDisconnection(Socket socket)
		{
			RaiseClientDisconnected(socket);

			long key = socket.Handle.ToInt64();
			if (_clients.ContainsKey(key))
			{
				_clients.Remove(key);
			}
		}

		public void DistributeMessage(byte[] buffer)
		{
			try
			{
				foreach (ConnectedClient connectedClient in _clients.Values)
				{
					connectedClient.Send(buffer);
				}
			}
			catch (SocketException se)
			{
			}
		}

		public void Close()
		{
			try
			{
				if (_socket != null)
				{
					_closed = true;

					// Close the clients
					foreach (ConnectedClient connectedClient in _clients.Values)
					{
						connectedClient.Stop();
					}

					_socket.Close();

					_socket = null;
				}
			}
			catch (ObjectDisposedException odex)
			{
			}
		}

		private void OnMessageRecived(Socket socket, byte[] buffer)
		{
			if (MessageRecived != null)
			{
				MessageRecived(socket, buffer);
			}
		}

		private void RaiseClientConnected(Socket socket)
		{
			if (ClientConnect != null)
			{
				ClientConnect(socket);
			}
		}

		private void RaiseClientDisconnected(Socket socket)
		{
			if (ClientDisconnect != null)
			{
				ClientDisconnect(socket);
			}
		}

		public class ConnectedClient
		{
			// Hold reference to client socket to allow sending messages to client
			private Socket m_clientSocket;
			SocketListener m_listener;

			public ConnectedClient(Socket clientSocket)
			{
				m_clientSocket = clientSocket;
				m_listener = new SocketListener();
			}

			// Register directly to SocketListener event
			public event TCPTerminal_MessageRecivedDel MessageRecived
			{
				add
				{
					m_listener.MessageRecived += value;
				}
				remove
				{
					m_listener.MessageRecived -= value;
				}
			}

			// Register directly to SocketListener event
			public event TCPTerminal_DisconnectDel Disconnected
			{
				add
				{
					m_listener.Disconnected += value;
				}
				remove
				{
					m_listener.Disconnected -= value;
				}
			}

			public void StartListen()
			{
				m_listener.StartReciving(m_clientSocket);
			}

			public void Send(byte[] buffer)
			{
				if (m_clientSocket == null)
				{
					throw new Exception("Can't send data. ConnectedClient is Closed!");
				}
				m_clientSocket.Send(buffer);

			}

			public void Stop()
			{
				m_listener.StopListening();
				m_clientSocket = null;
			}
		}
	}
}
